import { Injectable } from '@nestjs/common';
import { HttpResTemplate, HttpResult } from "@libs/http.result";
import { GroupMenus, MenuEntity, MenuGroupEntity } from "@entities/menu.entity";
import { InjectRepository } from "@nestjs/typeorm";
import { getConnection, Repository } from "typeorm";
import { listToTree, toTree } from "@utils/data";
import { CreateMenuDTO, CreateMenuGroupDTO, VerifyNameDTO } from "@dto/menu.dto";
import { SnowflakeId } from "@libs/snowflake.id";
import { format } from "@utils/date";
import { BusinessException, ParamsException, ServerException } from "@exceptions";
import { BatchDeleteDto } from "@dto/public.dto";
import { User } from "@entities/user.entity";

@Injectable()
export class MenuService {
    constructor(
        @InjectRepository(MenuEntity)
        private readonly menuRepository: Repository<MenuEntity>,

        @InjectRepository(MenuGroupEntity)
        private readonly menuGroupRepository: Repository<MenuGroupEntity>,

        @InjectRepository(GroupMenus)
        private readonly groupMenusRepository: Repository<GroupMenus>
    ) {}

    /**
     * 获取菜单列表
     */
    async getMenuList(): Promise<HttpResTemplate> {
        const menuList = await this.menuRepository.find({ where: { isDelete: 0 } })
        const menuTreeList = toTree(menuList, '0'); // 转为树形
        return HttpResult.success(menuTreeList)
    }

    /**
     * 获取菜单列表根据上级id
     * @param {Number} pid 上级id
     * @return {Promise<HttpResTemplate>} res
     */
    async getMenuListByParentId(pid: number): Promise<HttpResTemplate> {
        const menuList = await this.menuRepository.find({ where: { isDelete: 0 }})
        const menuMap = new Map() // 统计每个下面的子集数量
        for (let i = 0, len = menuList.length; i < len; i++) {
            const menu = menuList[i];
            if (!menuMap.has(menu.pid)) {
                menuMap.set(menu.pid, 0)
            }
            const count = menuMap.get(menu.pid)
            menuMap.set(menu.pid, Number(count)+1)
        }

        // 过滤出pid相同的菜单
        const filterMenuList = menuList.reduce((res: Array<MenuEntity>, menu: MenuEntity) => {
            // 与pid相同的
            if (+menu.pid === pid) {
                menu['hasChildren'] = !!+menuMap.get(menu.id) // 是否存在下级
                res.push(menu)
            }
            return res
        }, [])

        return HttpResult.success(filterMenuList)
    }

    /**
     * 获取菜单选项列表
     * @param {Number} pid 上级id
     * @return {Promise<HttpResTemplate>} res
     */
    async getMenuSelectOptions(pid: number): Promise<HttpResTemplate> {
        const menuList = await this.menuRepository.find({ where: { isDelete: 0 }})
        const menuMap = new Map() // 统计每个下面的子集数量
        for (let i = 0, len = menuList.length; i < len; i++) {
            const menu = menuList[i];
            if (!menuMap.has(menu.pid)) {
                menuMap.set(menu.pid, 0)
            }
            const count = menuMap.get(menu.pid)
            menuMap.set(menu.pid, Number(count)+1)
        }

        // 过滤出pid相同的菜单
        const filterMenuList = menuList.reduce((res: Array<any>, menu: MenuEntity) => {
            // 与pid相同的
            if (+menu.pid === pid) {
                const newMenu = {
                    id: menu.id,
                    label: menu.name,
                    // hasChildren: !!+menuMap.get(menu.id) // 是否存在下级
                    children: null
                }
                res.push(newMenu)
            }
            return res
        }, [])

        return HttpResult.success(filterMenuList)
    }

    /**
     * 创建菜单
     * @param {CreateMenuDTO} data 菜单数据
     */
    async createMenu(data: CreateMenuDTO): Promise<HttpResTemplate> {
        data.pid = data.pid ? +data.pid : 0
        // 验证名称是否重复
        if (await this.checkMenuNameExist(+data.pid, data.name)) {
            throw new ParamsException('菜单名重复')
        }
        enum MenuTypeEnum {
            directory = 0, // 目录
            page = 1, // 页面
            handle = 2 // 操作按钮
        }
        const newMenu = this.menuRepository.create(data)
        const menuType = newMenu.type
        newMenu.id = SnowflakeId.generate()
        newMenu.createTime = format(new Date())
        newMenu.createBy = 'admin'

        // 如果当前菜单为操作权限菜单
        if (menuType === MenuTypeEnum.handle) {
            newMenu.path = ''
            newMenu.isOutLink = 0
        }

        const save = await this.menuRepository.save(newMenu);
        return HttpResult.success(save)
    }

    /**
     * 验证菜单名称是否重复，同级菜单生效
     * @param {Number} pid
     * @param {String} name
     * @return {Promise<Boolean>}
     */
    async checkMenuNameExist(pid, name) {
        const find = await this.menuRepository.find({
            where: { pid: +pid, name: name }
        })
        if (find && find.length) return Promise.resolve(true)
        return Promise.resolve(false)
    }

    /**
     * 修改菜单
     * @param {Number} id 菜单id
     * @param {Object} menuData 菜单数据
     */
    async updateMenu(id: number, menuData: any): Promise<HttpResTemplate> {
        return HttpResult.success()
    }

    /**
     * 删除菜单
     * @param {String} id 菜单id
     */
    async deleteMenu(id:number): Promise<HttpResTemplate> {
        const testUserId = 744244887367680
        // 验证菜单是否有用户使用
        const curMenuIsUse = await this.checkUserMenuIsUse(testUserId, id)
        if (curMenuIsUse) {
            throw new BusinessException('当前菜单绑定了用户权限组，无法进行删除')
        }
        const find = await this.menuRepository.findOne({ where: { id, isDelete: 0 } })
        if (!find) throw new BusinessException('操作失败')
        await this.menuRepository.update(id, {
            isDelete: 1
        })
        return HttpResult.success()
    }

    /**
     * 批量删除菜单
     * @param {BatchDeleteDto} data
     */
    async batchDeleteMenu(data: BatchDeleteDto): Promise<HttpResTemplate> {
        const SQL = `UPDATE sys_menu SET is_delete=1 WHERE id in (${data.list.join(', ')})`
        const res = await this.menuRepository.query(SQL)
        return HttpResult.success(res.changedRows)
    }

    /**
     * 创建分组
     * @param {CreateMenuGroupDTO} data
     */
    async createGroup(data: CreateMenuGroupDTO): Promise<HttpResTemplate> {
        if (await this.checkGroupName(data.name)) {
            throw new BusinessException('分组名称已存在')
        }
        const newGroup = this.menuGroupRepository.create(data)
        newGroup.id = SnowflakeId.generate()
        newGroup.createBy = 'admin'
        newGroup.createTime = format(new Date())
        await this.menuGroupRepository.save(newGroup) // 保存分组
        await this.createGroupMenus(newGroup.id, data.menuList) // 创建分组中的菜单
        return HttpResult.success(newGroup)
    }

    /**
     * 验证分组名称是否重复
     * @param {String} groupName
     * @return {Promise<Boolean>}
     */
    async checkGroupName(groupName: string) {
        const res = await this.menuGroupRepository.findOne({
            where: { name: groupName}
        })
        return Promise.resolve(!!res)
    }

    /**
     * 创建分组中的菜单
     * @param {Number} groupId
     * @param {Array<Number>} menuList
     */
    private async createGroupMenus(groupId: number, menuList: Array<number>) {
        const values = menuList.map(mid => `(${SnowflakeId.generate()}, ${mid}, ${groupId})`).join(',')
        const SQL = `INSERT INTO sys_menu_group_menus (id, menu_id, menu_group_id) VALUES ${values}`
        const res = await this.groupMenusRepository.query(SQL)
        return Promise.resolve(res.affectedRows)
    }

    /**
     * 检测xxx用户xxx菜单是否被使用
     * @private
     * @param {Number} userId 用户id
     * @param {Number} menuId 菜单id
     * @return {Promise<Boolean>}
     */
    private async checkUserMenuIsUse(userId, menuId) {
        const SQL = `
            SELECT
                m.id 
            FROM
                sys_user u
                LEFT JOIN sys_menu_group mg ON u.auth_group_id = mg.id
                LEFT JOIN sys_menu_group_menus gm ON mg.id = gm.menu_group_id
                LEFT JOIN sys_menu m ON gm.menu_id = m.id 
            WHERE
                m.id = ${menuId} 
                AND u.id = ${userId} 
                AND mg.is_delete = 0 
                AND m.is_delete = 0
        `;
        const data = await this.menuRepository.query(SQL);
        return Promise.resolve(!!data.length)
    }

    /**
     * 获取分组下的用户数量
     */
    async getGroupUserCount(): Promise<HttpResTemplate> {
        const SQL = `
            SELECT
                g.id,
                g.name,
                count(u.auth_group_id) AS count 
            FROM
                sys_menu_group g
                LEFT JOIN sys_user u ON u.auth_group_id = g.id 
            WHERE
                g.is_delete = 0
            GROUP BY
                g.id;
            `;
        const data = await this.groupMenusRepository.query(SQL)
        return HttpResult.success(data)
    }

    /**
     * 验证名称
     * @param {VerifyNameDTO} data
     */
    async checkName(data: VerifyNameDTO): Promise<HttpResTemplate> {
        const res = await this.checkMenuNameExist(data.pid, data.name)
        return HttpResult.success(res)
    }

    /**
     * 生成前端路由
     * @param {Number} userId
     */
    async buildMenu(userId: number): Promise<HttpResTemplate> {
        try {
            const queryBuilder = getConnection().createQueryBuilder(MenuEntity, 'm')
            const menuList = await queryBuilder
                .leftJoinAndSelect(GroupMenus, 'gm', 'm.id=gm.menu_id')
                .leftJoinAndSelect(MenuGroupEntity, 'g', 'g.id=gm.menu_group_id')
                .leftJoinAndSelect(User, 'u', 'u.auth_group_id=g.id')
                .where(`u.is_delete=0 AND m.is_delete=0 AND g.is_delete=0`)
                .getMany()
            const menuTree = toTree(menuList, '0');
            return HttpResult.success(menuTree)
        } catch(e) {
            throw new ServerException(e)
        }
    }
}
